package src;

import java.io.Serializable;

abstract public class InstrMaqP implements Serializable{

	/** Atributos **/

		private static IApilaTrue iapilaTrue = null;
		private static IApilaFalse iapilaFalse = null;
		
		private static IEQ ieq = null;
		private static INEQ ineq = null;
		private static IGT igt = null;
		private static IGE ige = null;
		private static ILT ilt = null;
		private static ILE ile = null;

		private static ISuma isuma = null;
		private static IResta iresta = null;
		private static IOr ior = null;

		private static IMul imul = null;
		private static IDiv idiv = null;
		private static IAnd iand = null;

		private static IMenos imenos = null;
		private static INot inot = null;
		
		private static IDesapila idesapila = null;

		public enum ICOD {
			EQ, NEQ, GT, GE, LT, LE, SUM, RES, OR, 
			MUL, DIV, AND, MENOS, NOT, DESAPILA, 
			IR_F, IR_A, APILA_TRUE, APILA_FALSE, 
			APILA_NUM, APILA_DIR, COPIA, 
			APILA_IND, DESAPILA_IND, RESERVA, LIBERA, 
			MUEVE, IR_IND, ESCRIBE
		};

	/** Metodos **/

		abstract public ICOD ci();
		abstract public void ejecuta(VM vm);
		
		public int arg1() {
			throw new UnsupportedOperationException("arg1");
		}

		public static class IEQ extends InstrMaqP {
			
			private IEQ() {}
			public ICOD ci() {return ICOD.EQ;}
			public String toString() {return "EQ";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				if (op1.asInt() == op2.asInt())
					vm.push(new VM.IntPValue(1));
				else
					vm.push(new VM.IntPValue(0));					
				vm.incCP();
			}
		}

		public static class INEQ extends InstrMaqP {
			
			private INEQ() {}
			public ICOD ci() {return ICOD.NEQ;}
			public String toString() {return "NEQ";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				if (op1.asInt() != op2.asInt())
					vm.push(new VM.IntPValue(1));
				else
					vm.push(new VM.IntPValue(0));
				vm.incCP();				
			}
		}

		public static class IGT extends InstrMaqP {
			
			private IGT() {}
			public ICOD ci() {return ICOD.GT;}
			public String toString() {return "GT";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				if (op1.asInt() > op2.asInt())
					vm.push(new VM.IntPValue(1));
				else
					vm.push(new VM.IntPValue(0));
				vm.incCP();
			}
		}

		public static class IGE extends InstrMaqP {
			
			private IGE() {}
			public ICOD ci() {return ICOD.GE;}
			public String toString() {return "GE";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				if (op1.asInt() >= op2.asInt())
					vm.push(new VM.IntPValue(1));
				else
					vm.push(new VM.IntPValue(0));
				vm.incCP();
			}
		}

		public static class ILT extends InstrMaqP {
			
			private ILT() {}
			public ICOD ci() {return ICOD.LT;}
			public String toString() {return "LT";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				if (op1.asInt() < op2.asInt())
					vm.push(new VM.IntPValue(1));
				else
					vm.push(new VM.IntPValue(0));
				vm.incCP();
			}
		}

		public static class ILE extends InstrMaqP {
			
			private ILE() {}
			public ICOD ci() {return ICOD.LE;}
			public String toString() {return "LE";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				if (op1.asInt() <= op2.asInt())
					vm.push(new VM.IntPValue(1));
				else
					vm.push(new VM.IntPValue(0));
				vm.incCP();
			}
		}

		public static class ISuma extends InstrMaqP {
			
			private ISuma() {}
			public ICOD ci() {return ICOD.SUM;}
			public String toString() {return "SUMA";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				vm.push(new VM.IntPValue(op1.asInt()+op2.asInt()));
				vm.incCP();
			}
		}

		public static class IResta extends InstrMaqP {
			
			private IResta() {}
			public ICOD ci() {return ICOD.RES;}
			public String toString() {return "RESTA";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				vm.push(new VM.IntPValue(op1.asInt()-op2.asInt()));
				vm.incCP();
			}
		}

		public static class IOr extends InstrMaqP {
			
			private IOr() {}
			public ICOD ci() {return ICOD.OR;}
			public String toString() {return "OR";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				if (op1.asInt() == 0 && op2.asInt() == 0)
					vm.push(new VM.IntPValue(0));
				else
					vm.push(new VM.IntPValue(1));
				vm.incCP();
			}
		}

		public static class IMul extends InstrMaqP {
			
			private IMul() {}
			public ICOD ci() {return ICOD.MUL;}
			public String toString() {return "MUL";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				vm.push(new VM.IntPValue(op1.asInt()*op2.asInt()));
				vm.incCP();
			}
		}

		public static class IDiv extends InstrMaqP {
			
			private IDiv() {}
			public ICOD ci() {return ICOD.DIV;}
			public String toString() {return "DIV";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				vm.push(new VM.IntPValue(op1.asInt()/op2.asInt()));
				vm.incCP();
			}
		}

		public static class IAnd extends InstrMaqP {
			
			private IAnd() {}
			public ICOD ci() {return ICOD.AND;}
			public String toString() {return "AND";}

			public void ejecuta(VM vm) {
				VM.PValue op2 = vm.pop();
				VM.PValue op1 = vm.pop();
				if (op1.asInt() == 0 || op2.asInt() == 0)
					vm.push(new VM.IntPValue(0));
				else
					vm.push(new VM.IntPValue(1));
				vm.incCP();		
			}
		}

		public static class IMenos extends InstrMaqP {
			
			private IMenos() {}
			public ICOD ci() {return ICOD.MENOS;}
			public String toString() {return "MENOS";}

			public void ejecuta(VM vm) {
				VM.PValue op = vm.pop();
				vm.push(new VM.IntPValue(-op.asInt()));
				vm.incCP();
			}
		}

		public static class INot extends InstrMaqP {
			
			private INot() {}	
			public ICOD ci() {return ICOD.NOT;}
			public String toString() {return "NOT";}

			public void ejecuta(VM vm) {
				VM.PValue op = vm.pop();
				if (op.asInt() == 0)
					vm.push(new VM.IntPValue(1));
				else
					vm.push(new VM.IntPValue(0));
				vm.incCP();
			}
		}

		public static class IApilaTrue extends InstrMaqP {
			
			private IApilaTrue() {}
	        public ICOD ci() {return ICOD.APILA_TRUE;}
	        public String toString() {return "apila_true()";}

			public void ejecuta(VM vm) {
				vm.push(new VM.BooleanPValue(true));
				vm.incCP();
			}
		}

		public static class IApilaFalse extends InstrMaqP {
			
			private IApilaFalse() {}
	        public ICOD ci() {return ICOD.APILA_FALSE;}
	        public String toString() {return "apila_false()";}

			public void ejecuta(VM vm) {
				vm.push(new VM.BooleanPValue(false));
				vm.incCP();
			}
		}

		public static class IApilaNum extends InstrMaqP {
			
			private int val;
			private IApilaNum(String val) {
				try {
					this.val = Integer.parseInt(val);
				} catch (NumberFormatException e) {
					this.val = 0;
				}
			}
			
			private IApilaNum(int val) {
				this.val = val;
			}

			public ICOD ci() {return ICOD.APILA_NUM;}
			public int arg1() {return val;}
			public String toString() {return "apila_num(" + val + ")";
			}

			public void ejecuta(VM vm) {
				vm.push(new VM.IntPValue(val));
				vm.incCP();
			}
		}
		
		public static class IApilaDir extends InstrMaqP {

			private int val;

			private IApilaDir(String val) {
				try {
					this.val = Integer.parseInt(val);
				} catch (NumberFormatException e) {
					this.val = 0;
				}
			}

			public IApilaDir(int val) {
				this.val = val;
			}

			public ICOD ci() {return ICOD.APILA_DIR;}
			public int arg1() {return val;}
			public String toString() {return "apila_dir(" + val + ")";}

			public void ejecuta(VM vm) {
				vm.apila_dir(val);
				vm.incCP();				
			}
		}

		public static class IDesapilaDir extends InstrMaqP {
			private int val;

			private IDesapilaDir(String val) {
				try {
					this.val = Integer.parseInt(val);
				} catch (NumberFormatException e) {
					this.val = 0;
				}
			}

			public IDesapilaDir(int val) {
				this.val = val;
			}

			public ICOD ci() {return ICOD.DESAPILA;}
			public int arg1() {return val;}
			public String toString() {return "desapila_dir(" + val + ")";}

			public void ejecuta(VM vm) {
				vm.desapila_dir(val);
				vm.incCP();
			}
		}
	
				
		public static class IIr_a extends InstrMaqP {

			private int val;

			private IIr_a(int val) {
				this.val = val;
			}
	
			public ICOD ci() {return ICOD.IR_A;}	
			public int arg1() {return val;}	
			public String toString() {return "ir_a(" + val + ")";}

			public void ejecuta(VM vm) {
				vm.cambioCP(val);
			}

		}
	
		public static class IIr_f extends InstrMaqP {

			private int val;
			
			private IIr_f(int val) {
				this.val = val;
			}
	
			public ICOD ci() {return ICOD.IR_F;}	
			public int arg1() {return val;}	
			public String toString() {return "ir_f(" + val + ")";}

			public void ejecuta(VM vm) {
				VM.PValue cond = vm.pop();
				if (cond.asInt() == 0)
					vm.cambioCP(val);
				else
					vm.incCP();				
			}

		}
		
		public static class IIr_ind extends InstrMaqP{

			public IIr_ind() {}
			public ICOD ci() { return ICOD.IR_IND;}
	        public String toString() {return "ir_ind()";}

			public void ejecuta(VM vm) {
				VM.PValue dir = vm.pop();
                vm.cambioCP(dir.asInt());				
			}
			
		}
		
		public static class ICopia extends InstrMaqP {

			public ICopia() {}

			public ICOD ci() { return ICOD.COPIA;}
			public String toString() { return "copia()"; }

			public void ejecuta(VM vm) {
				VM.PValue dato = vm.pop();
				vm.push(dato);
				vm.push(dato);
				vm.incCP();
			}
			
		}
				
		public static class IApilaInd extends InstrMaqP{

			public IApilaInd() {}

			public ICOD ci() { return ICOD.APILA_IND;}
	        public String toString() {return "apila_ind()";}

			public void ejecuta(VM vm) {
				VM.PValue op1 = vm.pop();
                 
				if (op1.asInt()<0)
					try {
						throw new Exception("Acceso a una posicion " +
                        		"de memoria erronea (" +op1.asInt()+" )");
					} catch (Exception e) {
						// TODO Generalizar sistema de errores
						e.printStackTrace();
					}
                
                vm.apila_dir(op1.asInt());
                vm.incCP();
			}
			
		}
				
		public static class IDesapilaInd extends InstrMaqP{

			public IDesapilaInd() {}
			public ICOD ci() { return ICOD.DESAPILA_IND;}
			public String toString() {return "desapila_ind()";}
			
			public void ejecuta(VM vm) {
				
				VM.PValue op2 = vm.pop();
                VM.PValue op1 = vm.pop();       
                
                //TODO if (op1.asInt()<0) 
                
                vm.push(op2);
                vm.desapila_dir(op1.asInt());
                vm.incCP();	
			}			
		}
		
		public static class IDesapila extends InstrMaqP{

			public IDesapila() {}
			public ICOD ci() { return ICOD.DESAPILA;}
			public String toString() {return "desapila()";}
			
			public void ejecuta(VM vm) {
				
				vm.pop();
                vm.incCP();	
			}			
		}
		
		public static class IEscribe extends InstrMaqP{

			public IEscribe() {
				super();
			}

			public ICOD ci() { return ICOD.ESCRIBE;}
			public String toString() { return "escribe()";}

			public void ejecuta(VM vm) {
				VM.PValue op1 = vm.pop();
				System.out.println(op1.asInt());
				vm.incCP();
			}
			
		}
				
		public static class IMueve extends InstrMaqP {

			private int tam;
			
			public IMueve(int tam) {
				this.tam = tam;
			}
			
			public ICOD ci() { return ICOD.MUEVE;}
			public String toString() {return "mueve()";}
			
			public void ejecuta(VM vm) {
				VM.PValue op1 = vm.pop();
                VM.PValue op2 = vm.pop();
                int origen = op1.asInt();
                int destino = op2.asInt();
                for(int i = 0; i < tam; i++){
                	vm.apila_dir(origen+i);
                	vm.desapila_dir(destino+i);                	
                }
                vm.incCP();
            }
			
		}
		
		public static InstrMaqP nuevaIEQ() {
			if (ieq == null) {
				ieq = new IEQ();
			}
			return ieq;
		}
	
		public static InstrMaqP nuevaINEQ() {
			if (ineq == null) {
				ineq = new INEQ();
			}
			return ineq;
		}
	
		public static InstrMaqP nuevaIGT() {
			if (igt == null) {
				igt = new IGT();
			}
			return igt;
		}
	
		public static InstrMaqP nuevaIGE() {
			if (ige == null) {
				ige = new IGE();
			}
			return ige;
		}
	
		public static InstrMaqP nuevaILT() {
			if (ilt == null) {
				ilt = new ILT();
			}
			return ilt;
		}
	
		public static InstrMaqP nuevaILE() {
			if (ile == null) {
				ile = new ILE();
			}
			return ile;
		}
	
		public static InstrMaqP nuevaISuma() {
			if (isuma == null) {
				isuma = new ISuma();
			}
			return isuma;
		}
	
		public static InstrMaqP nuevaIResta() {
			if (iresta == null) {
				iresta = new IResta();
			}
			return iresta;
		}
	
		public static InstrMaqP nuevaIOr() {
			if (ior == null) {
				ior = new IOr();
			}
			return ior;
		}
	
		public static InstrMaqP nuevaIMul() {
			if (imul == null) {
				imul = new IMul();
			}
			return imul;
		}
	
		public static InstrMaqP nuevaIDiv() {
			if (idiv == null) {
				idiv = new IDiv();
			}
			return idiv;
		}
	
		public static InstrMaqP nuevaIAnd() {
			if (iand == null) {
				iand = new IAnd();
			}
			return iand;
		}
	
		public static InstrMaqP nuevaIMenos() {
			if (imenos == null) {
				imenos = new IMenos();
			}
			return imenos;
		}
	
		public static InstrMaqP nuevaINot() {
			if (inot == null) {
				inot = new INot();
			}
			return inot;
		}
		
		public static InstrMaqP nuevaIApilaNum(String val) {
			return new IApilaNum(val);
		}
		
		public static InstrMaqP nuevaIApilaNum(int val) {
			return new IApilaNum(val);
		}
		
		public static InstrMaqP nuevaIApilaDir(String val) {
			return new IApilaDir(val);
		}
		
		public static InstrMaqP nuevaIApilaDir(int arg) {
			return new IApilaDir(arg);
		}
		
		public static InstrMaqP nuevaIDesapilaDir(String val) {
			return new IDesapilaDir(val);
		}
		
		public static InstrMaqP nuevaIDesapilaDir(int val) {
			return new IDesapilaDir(val);
		}
		
		public static InstrMaqP nuevaIIr_a(int val) {
			return new IIr_a(val);
		}		

		public static InstrMaqP nuevaIIr_f(int val) {
			return new IIr_f(val);
		}

		public static InstrMaqP nuevaIIr_ind() {
			return new IIr_ind();
		}

		public static InstrMaqP nuevaIMueve(int tam) {
			return new IMueve(tam);
		}

		public static InstrMaqP nuevaICopia() {
			return new ICopia();
		}

		public static InstrMaqP nuevaIApilaInd() {
			return new IApilaInd();
		}

		
		public static InstrMaqP nuevaIDesapilaInd() {
			return new IDesapilaInd();
		}
		
		public static InstrMaqP nuevaIEscribe() {
			return new IEscribe();
		}
		public static InstrMaqP nuevaIDesapila() {
			if (idesapila == null) {
				idesapila = new IDesapila();
			}
			return idesapila;
		}


}